Hello World with Terraform
This article talks about Terraform, an open source infrastructure automation tool by HashiCorp in a 101-introductory fashion.
What is Terraform?
Terraform is an Infrastructure As Code (IaC) tool which allows users to provision infrastructure through code. In simple words, whatever resources ( infrastructure ) an application needs, can be build using terraform programmatically. Terraform allows programmers to provision, change, and version infrastructure automatically and efficiently.
The principle (IaC) behind terraform comes from the Agile and DevOps world with aims to make infrastructure provisioning in an automated and repeated manner, and thus taking away error-prone manual configuration and management.
What makes Terraform so special?
The following features make terraform so unique and amazing:-
- Declarative:- Terraform language is declarative in nature, which means we tell terraform what we desire as an end state, and it will handle the execution on its own, which is unlike the case in imperative style where we need to define every single step on how to perform a task.
- Providers:- Terraform supports a lot of IaaS, Saas, Paas providers. Providers are a logical abstraction of an upstream API. They are responsible for understanding API interactions and exposing resources.
- State:- Terraform stores and maintain the current state of your infrastructure and configuration. This state is used to avoid configuration discrepancies when a new resource is added to existing infrastructure or some modifications take place.
Deploy EC2 using Terraform
- This blog deploys an EC2 instance configured with an apache server and security group which allows HTTP and SSH traffic only.
Pre-requisites
- Terraform's config files where you define resources have a .tf extension, and this blog uses Visual studio code and Terraform extension in vscode by HashiCorp.
- This blog uses start.tf as the main config file and Terraform v0.15.4 version.
- aws cli must be configured.
Define a provider
- Defining a plugin allows us to talk to a specific set of API for a particular service. Terraform will figure out what provider plugins needs to be installed based on provider configuration.
terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 3.0" } } }
Configure the provider
- Terraform requires us to configure the provider with proper credentials before we can use it.
- From a security point of view, using IAM role + MFA with aws cli is considered to be best practice, hence created a script that automates configuration of terraform ( repository for script).
provider "aws" { region = "ap-southeast-1" //region where resources need to be deployed }
Defining Resource blocks
- Another important component of terraform config file which describes one or more infrastructure objects like EC2, Load Balancer, VPC, etc.
- A
resource block
declares a resource of a given type ("aws_instance") with a given local name ("terraform-ec2"). The name is used to refer to this resource from elsewhere in the config file but has no meaning outside the config file's scope. - The resource type and name together serve as an identifier for a given resource.
- Following codes are the declaration for accessing ami id, and security group with inbound and outbound rules.
#for accesing ami id for particular region data "aws_ami" "instance_id" { owners = ["amazon"] most_recent = true filter { name = "name" values = ["amzn2-ami-hvm-*-x86_64-ebs"] } } # for declaring security group for ec2 instance resource resource "aws_security_group" "allow_web" { name = "allow_web.traffic" description = "Allow TLS web traffic" ingress { description = "SSH from VPC" from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { description = "HTTP from VPC" from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" //any protocol cidr_blocks = ["0.0.0.0/0"] ipv6_cidr_blocks = ["::/0"] } tags = { Name = "Created by Terraform" } }
- The following declaration is about ec2 resource with all the necessary parameters like ami id, security group, user data, key, instance type, availability zone and tags.
resource "aws_instance" "terraform-ec2" { ami = data.aws_ami.instance_id.id instance_type = "t2.micro" availability_zone = "ap-southeast-1a" key_name = "jatin-marek-learning1-key-pair" security_groups = [aws_security_group.allow_web.name] user_data = <<-EOF #! /bin/bash sudo yum update sudo yum install -y httpd sudo systemctl start httpd sudo systemctl enable httpd echo " <h1>Deployed via Terraform</h1> " | sudo tee /var/www/html/index.html EOF tags = { Name = "Created by Terraform" } }
Referencing resources in config file
- We can reference other resources which are being defined in our config code using reference expressions.
- The syntax is as follows
<RESOURCE TYPE>.<NAME>.<property>
represents a managed resource of the given type and name and its property. - The following lines show how resources are referenced.
resource "aws_instance" "terraform-ec2" { ami = data.aws_ami.instance_id.id security_groups = [aws_security_group.allow_web.name] }
Terraform Commands and files
- Just like any other program language we need to run/execute the written code, in terraform world, this means to generate a state in the form of a blueprint and then apply it to spin up the resources to build our infrastructure.
- There is a general workflow that every terraform project follows. Divided into 3 stages.
Code/init stage
- In this stage, the providers and resources are declared which we have done in our start.tf file.
terraform init
- After writing our resource declaration this command looks at config to download required plugins to interact with our provider by creating a .terraform directory for storing all provider-related code.
Plan Stage
- This stage creates an execution plan, which is like a preview of changes that Terraform plans to make to your infrastructure. By default, when Terraform creates a plan it:
- Reads the current state of any already-existing remote objects to make sure that the Terraform state is up-to-date.
- Compares the current configuration to the prior state and noting any differences.
- Proposes a set of change actions that should, if applied, make the remote objects match the configuration.
- Terraform state is stored in terraform.tfstate file, this file should NOT be edited by the user directly.
terraform plan
Apply/Destroy Stage
- In this stage, terraform spins up the resources using cloud provider API and output some variables either autogenerated or user-defined.
- Order of declaration is not important (with some exceptions) as terraform is smart enough to decide that order during provisioning of infrastructure.
- Terraform's execution should not be assumed as a step-wise execution, terraform uses a blueprint for execution so whatever resources in the plan are new/modified/deleted, only those will be touched
- start.tf file before running apply command looks like this .
terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 3.0" } } } # Configure the AWS Provider provider "aws" { region = "ap-southeast-1" //region where resources need to be deployed } variable "tag_for_ec2" { description = "Tags for ec2" type = any } data "aws_ami" "instance_id" { owners = ["amazon"] most_recent = true filter { name = "name" values = ["amzn2-ami-hvm-*-x86_64-ebs"] } } resource "aws_instance" "terraform-ec2" { ami = data.aws_ami.instance_id.id instance_type = "t2.micro" availability_zone = "ap-southeast-1a" key_name = "jatin-marek-learning1-key-pair" security_groups = [aws_security_group.allow_web.name] user_data = <<-EOF #! /bin/bash sudo yum update sudo yum install -y httpd sudo systemctl start httpd sudo systemctl enable httpd echo " <h1>Deployed via Terraform</h1> " | sudo tee /var/www/html/index.html EOF tags = { Name = "Created by Terraform" } } resource "aws_security_group" "allow_web" { name = "allow_web.traffic" description = "Allow TLS web traffic" ingress { description = "SSH from VPC" from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { description = "HTTP from VPC" from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" //any protocol cidr_blocks = ["0.0.0.0/0"] ipv6_cidr_blocks = ["::/0"] } tags = { Name = "Created by Terraform" } }
terraform apply //to create resources for the infrastructure.
- To destroy resources there are 2 ways:
- either run destroy command.
-
terraform destroy //to destroy resources from the infrastructure.
- Delete the code in the config file and run
terraform apply
since terraform's execution depends on the blueprint/plan.
- We can confirm our provision in the aws console by accessing our website.
Conclusion
Here we saw how terraform helps in automating the provisioning of resources in the cloud. In the future playing with terraforming can go into more depth like using variables, targeting specific resources for deployment, conditional expressions, etc.
Till then happy learning.